/****************************************************************************
    Copyright 2009 Clark Scheff
    
    This file is part of Boulder Crush.

    Boulder Crush is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation version 3 of the License.

    Boulder Crush is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Boulder Crush.  If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/

package com.scheffsblend.boulder.crash;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.*;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;

import java.util.Random;
import java.util.Vector;

/**
 * @author lithium
 *
 */
public class GraphView extends View implements SensorListener, Runnable
{
	private static String TAG = "Tires";
	private static int FRAME_WIDTH = 60;
	private static int FRAME_HEIGHT = 110;
	
	private static int PLAYER_STANDING = 0;
	private static int PLAYER_MOVING = 1;
	private static int PLAYER_CROUCHING = 2;
	
	private static int DEFAULT_LIVES = 3;
	private static int DEFAULT_LEVEL = 1;
	private static float DEFAULT_VELOCITY_SCALE = 0.5f;
	
	private static int ONE_UP_TIME = 80;
	private static int LEVEL_UP_TIME = 120;
	
	private static int ONE_UP = 50;
	
	private static int FACING_RIGHT = 0;
	private static int FACING_LEFT = 1;
	
	private static int BOUNDING_BOX_FEATHER = 20;
	
	private static int GAMESTATE_INTRO = 0;
	private static int GAMESTATE_BEGIN = 1;
	private static int GAMESTATE_PLAYING = 2;
	private static int GAMESTATE_PLAYER_HIT = 3;
	private static int GAMESTATE_OVER = 4;
	
	private static int TIME_INTRO = 0;
	private static int TIME_BEGIN = 2000;
	private static int TIME_PLAYING = 0;
	private static int TIME_PLAYER_HIT = 1000;
	private static int TIME_GAME_OVER = 2000;
		
	private Bitmap  mBitmap;
    private Bitmap  mPlayer;
    private Bitmap 	mTempPlayer;
    private Bitmap	mBackground;
    private Bitmap	mTire;
    private Paint   mPaint = new Paint();
    private Paint	mTextPaint1 = new Paint();
    private Paint	mTextPaint2 = new Paint();
    private Canvas  mCanvas = new Canvas();
    private Path    mPath = new Path();
    private RectF   mRect = new RectF();
    private int     mColors[] = new int[3*2];
    private int		mWidth = 480;
    private int		mHeight = 320;
    private int		mGameState = GAMESTATE_BEGIN;
    private long	mStateTimer = 0;
    private long	mStartTime = 0;
    private final float mAccelMultiplier = 3.0f;
    private Paddle 	mPaddle;
    private GradientDrawable mBackgradient;
    private Vector<Boulder> mBoulders = new Vector<Boulder>();
    private long	mLastBoulderTime = 0;
    private int		mAnimIndex = 0;
    private int		mAnimTime = 75;
    private long	mLastAnimTime = 0;
    private int		mPlayerWidth = 48;//FRAME_WIDTH;//FRAME_WIDTH / 2;
    private int		mPlayerHeight = 88;//FRAME_HEIGHT;//FRAME_HEIGHT / 2;
    private Canvas  mPlayerCanvas = new Canvas();
    private int		mPlayerState = PLAYER_STANDING;
    private int		mDirection = FACING_LEFT;
    private float	mVelocityScale = DEFAULT_VELOCITY_SCALE;
    private int 	mBouldersCleared = 0;
    private int		mLevelNumber = 1;
    private int		mNextLevel = 10;
    private boolean	mPaused = false;
    private boolean mSounds = true;
    private SoundPool	mSoundPool;
    private int		mBoingSound;
    private int 	mDohSound;
    private Thread	mThread = null;
    private int 	mLastFrame = 0;
    private int		mLevelUp = 0;
    private int		mGameOver = 0;
    private int		mOneUp = 0;
    private int		mLives = 3;
    private Typeface mFace;
    private Point p = new Point();
    private Point v = new Point();
    private int accel;
    private float x;
    private float y;
    private float vx;
    private float vy;
    private float r;
    private Boulder b;
    private Random rand = new Random();
    private boolean mRunning = false;
    private String mStrLives = "3";
    private String mStrLevel = "1";
    private String mStrScore = "0";
    private float mTireCenter = 0;
    private Button mTestButton;
    
    public GraphView(Context context) {
        super(context);
        mSoundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
        initSounds();
        long time = System.currentTimeMillis();
        while(System.currentTimeMillis()-time > 2000);

        mFace = Typeface.createFromAsset(getContext().getAssets(),
                "fonts/casual.ttf");
        mPaint.setTypeface(mFace);

        mColors[0] = Color.argb(192, 255, 64, 64);
        mColors[1] = Color.argb(192, 64, 128, 64);
        mColors[2] = Color.argb(192, 64, 64, 255);
        mColors[3] = Color.argb(192, 64, 255, 255);
        mColors[4] = Color.argb(192, 128, 64, 128);
        mColors[5] = Color.argb(192, 255, 255, 64);
        
        mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
        mPaint.setFilterBitmap(true);
//        mPaint.setDither(true);
        mRect.set(-0.5f, -0.5f, 0.5f, 0.5f);
        mPath.arcTo(mRect, 0, 180);
        
        mPaddle = new Paddle();
        
        mPlayer = BitmapFactory.decodeResource(getResources(), R.drawable.walking_anim);
        mTire = BitmapFactory.decodeResource(getResources(), R.drawable.tire);
        mTireCenter = mTire.getWidth()/2;
    }
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
        mBackground = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
        mCanvas.setBitmap(mBitmap);
        mCanvas.drawColor(0xFFFFFFFF);
        mWidth = w;
        mHeight = h;
        mPaddle.setPosition((w >> 1) - mPlayerWidth, h - mPlayerHeight);
        Rect bounds = new Rect(0,0,(int)mWidth,(int)mHeight);
        mPaddle.setBounds(bounds);
        mPaddle.setDimensions(mPlayerWidth, mPlayerHeight);
        mTempPlayer = Bitmap.createBitmap(mPlayerWidth, mPlayerHeight, Bitmap.Config.ARGB_8888);
        mPlayerCanvas.setBitmap(mTempPlayer);
        generateBackground();

        mPaint.setColor(0xFF000000);
        mTextPaint1.setColor(0xF000FF00);
        mTextPaint1.setTextSize(20);
        mTextPaint1.setTypeface(mFace);
        mTextPaint1.setFlags(Paint.ANTI_ALIAS_FLAG);
        mTextPaint2.setColor(0xFFFF4020);
        mTextPaint2.setTextSize(20);
        mTextPaint2.setTypeface(mFace);
        mTextPaint2.setFlags(Paint.ANTI_ALIAS_FLAG);

        this.setOnKeyListener( new View.OnKeyListener()
    	{
    		@Override
    		public boolean onKey(View v, int keyCode, KeyEvent event) {
    			if(mGameOver == 0 && keyCode == KeyEvent.KEYCODE_DPAD_CENTER && 
    					event.getAction() == KeyEvent.ACTION_DOWN) {
    				
    				mPlayerState = PLAYER_CROUCHING;
    			}
    			else
    				mPlayerState = PLAYER_STANDING;
    				
    			return false;
    		}
    	});
        initGame();
        this.invalidate();
        long time = System.currentTimeMillis();
        while((System.currentTimeMillis() - time) < 1000);
        mThread = new Thread(this);
        mRunning = true;
        mThread.start();
        
        mTestButton = new Button(this.getContext());
        mTestButton.setWidth(100);
        mTestButton.setHeight(50);
        super.onSizeChanged(w, h, oldw, oldh);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        synchronized (this) {
            if (mBitmap != null) {
                canvas.drawBitmap(mBackground, 0, 0, null);
                mPaint.setColor(0xF0000000);
                mPaint.setTextSize(20);
                mPaint.setStrokeWidth(2);
                canvas.drawText(mStrScore, 102, 27, mPaint);
                canvas.drawText(mStrLevel, 442, 27, mPaint);
                canvas.drawText(mStrLives, 272, 27, mPaint);
                canvas.drawText(mStrScore, 100, 25, mTextPaint1);
                canvas.drawText(mStrLevel, 440, 25, mTextPaint1);
                canvas.drawText(mStrLives, 270, 25, mTextPaint2);
                drawBoulders(canvas);

                if(mGameState == GAMESTATE_PLAYER_HIT && System.currentTimeMillis()%2 == 1)
                	mPaint.setColor(0x80FFFF00);
                else
                	mPaint.setColor(0xFF000000);
                drawPlayer(canvas);
                
                if(mLevelUp > 0) {
                	mPaint.setTextSize(50);
                	mPaint.setColor(Color.argb((int)((float)(mLevelUp)/(float)LEVEL_UP_TIME*255), 255, 0, 255));
                	canvas.drawText("Level Up", 100, mHeight/2 + 10, mPaint);
                	mLevelUp--;
                    mPaint.setColor(0xFF000000);
                }
                if(mOneUp > 0) {
                	p = mPaddle.getPosition();
                	p.y = mHeight - mPlayerHeight;
                	mPaint.setTextSize(20);
                	mPaint.setColor(Color.argb((int)((float)(mOneUp)/(float)ONE_UP_TIME*255), 0, 0, 0));
                	canvas.drawText("1-Up", p.x+2, p.y+2, mPaint);
                	mPaint.setColor(Color.argb((int)((float)(mOneUp)/(float)ONE_UP_TIME*255), 224, 0, 224));
                	canvas.drawText("1-Up", p.x, p.y, mPaint);
                	mOneUp--;
                    mPaint.setColor(0xFF000000);
                }
                if(mGameOver > 0) {
                	mPaint.setTextSize(50);
                	mPaint.setColor(Color.argb((int)((float)(mGameOver)/100f*255), 255, 0, 0));
                	canvas.drawText("Game Over", 60, mHeight/2 + 10, mPaint);
                	mGameOver--;
                	if(mGameOver == 0)
                		initGame();
                    mPaint.setColor(0xFF000000);
                }
                mTestButton.draw(canvas);
                if(mGameState == GAMESTATE_BEGIN) {
                	p = mPaddle.getPosition();
                	p.y = mHeight - mPlayerHeight;
                	mPaint.setTextSize(20);
                	mPaint.setColor(0XFF000000);
                	canvas.drawText(" !!!", p.x+2, p.y+2, mPaint);
                	mPaint.setColor(0XFFFF0000);
                	canvas.drawText(" !!!", p.x, p.y, mPaint);
                    mPaint.setColor(0xFF000000);
                }
            } 
//            invalidate();
        }
    }

    private void generateBackground() {
//        mBackgradient = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM,
//				new int[] { 0xC00094BF, 0x40F0F0F0 });
//        mBackgradient.setShape(GradientDrawable.LINEAR_GRADIENT);
//        mBackgradient.setBounds(0, 0, mWidth, mHeight);
        Bitmap backCanvas = BitmapFactory.decodeResource(getResources(), R.drawable.bg);
        
        Canvas canvas = new Canvas();
        canvas.setBitmap(mBackground);
        canvas.drawBitmap(backCanvas, 0, 0, mPaint);
        backCanvas.recycle();
//        mBackgradient.draw(canvas);
        mPaint.setColor(0xF0000000);
        mPaint.setTextSize(20);
        mPaint.setStrokeWidth(2);
        canvas.drawText("Score:", 7, 27, mPaint);
        canvas.drawText("Level:", 362, 27, mPaint);
        canvas.drawText("Lives:", 192, 27, mPaint);
        mPaint.setColor(0xF000FF00);
        canvas.drawText("Score:", 5, 25, mPaint);
        canvas.drawText("Level:", 360, 25, mPaint);
        mPaint.setColor(0xFFFF4020);
        canvas.drawText("Lives:", 190, 25, mPaint);
    }
    
    
    public void onSensorChanged(int sensor, float[] values) {
        synchronized (this) {
           	if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
           		accel = (int)(values[SensorManager.RAW_DATA_Y] * -mAccelMultiplier);
          		if(mPlayerState != PLAYER_CROUCHING)
           			mPaddle.setVelocity(accel, 0);
           		else
           			mPaddle.setVelocity(0, 0);
          			
           		if(accel > 0)
       				mDirection = FACING_RIGHT;
       			else if(accel< 0)
       				mDirection = FACING_LEFT;

       			if(accel != 0 && mPlayerState != PLAYER_CROUCHING) {
           			mPlayerState = PLAYER_MOVING;
              	}
            }
        }
    }

    // this method will execute once thread.start is called.  This method
    // will notify the handler that it is time to update the game
    public void run() {
    	while(mRunning) {
   			handler.sendEmptyMessage(0);
   			try {
				// allow the thread to sleep a bit and allow other threads to run
   				// 17 milliseconds will allow for a frame rate of about 60 FPS.
   				Thread.sleep(17);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    	}
    }
    
    private Handler handler = new Handler() {
    	@Override
    	public void handleMessage(Message msg) {
       		
       		if(false == mPaused) {
           		mPaddle.update();
           		if(mGameState != GAMESTATE_BEGIN)
           			manageBoulders();
           		animatePlayer();
           		updateGameState();
       		}
       		
   			invalidate();
    	}
    };

	@Override
	public void onAccuracyChanged(int arg0, int arg1) {
		
	}
	
	private void manageBoulders() {
		synchronized(this) {
		if(System.currentTimeMillis() - mLastBoulderTime >= (rand.nextInt(1000)+1000/mVelocityScale)) {
			b = new Boulder();
			
			r = rand.nextInt(Boulder.MAX_RADIUS - Boulder.MIN_RADIUS) + 
					Boulder.MIN_RADIUS;
			x = -(rand.nextInt((int)r))-(r+5.0f);
			y = rand.nextInt((int)(mHeight-Boulder.MAX_RADIUS*3));// + mHeight - r;
			
			vx = rand.nextInt(10) + 8;
			vy = 0;//-(rand.nextInt(Boulder.MAX_VELOCITY - Boulder.MIN_VELOCITY) +\
			if(y > mHeight/3)
				vy += y/10;
			
			b.setPosition(x, y);
			if(mVelocityScale <= 1.0f)
				b.setVelocity(vx*mVelocityScale, vy*mVelocityScale);
			else if(mVelocityScale <= 1.5f)
				b.setVelocity(vx, vy*mVelocityScale);
			else
				b.setVelocity(vx, vy*1.5f);
			b.setRadius(r);
			
			mBoulders.add(b);
			mLastBoulderTime = System.currentTimeMillis();
		}
		
		for(int i = 0; i < mBoulders.size(); i++) {
			b = mBoulders.get(i);
			b.move();
			p = b.getPosition();
			v = b.getVelocity();
			r = b.getRadius();
			if( (p.x-r) > mWidth ) {
				mBoulders.remove(i--);
				mBouldersCleared++;
				mStrScore = "" + mBouldersCleared;
				if(mBouldersCleared % ONE_UP == 0) {
					mOneUp = ONE_UP_TIME;
					mLives++;
					mStrLives = "" +mLives;
				}
				if(mBouldersCleared > 0 && (mBouldersCleared%mNextLevel) == 0) {
					mVelocityScale = mVelocityScale + 0.075f;
					mLevelNumber++;
					mNextLevel += (mLevelNumber*10);
					mLevelUp = LEVEL_UP_TIME;
					mStrLevel = "" + mLevelNumber;
				}
			} else {
				float rate = (-0.01f * r) + 1.40f;
				if( (p.y+r) >= mHeight+5 ) {
					v.y = -v.y*0.95f;
					b.setVelocity(v.x, v.y);
					b.setPosition(p.x, mHeight - r);
					if(mSounds) {
						mSoundPool.play(mBoingSound, 0.5f, 0.5f, 0, 0, rate);
					}
				}
				if(mGameState == GAMESTATE_PLAYING && 
						collideWithPlayer(mPaddle.getPosition(), p, r)) {
					if(mSounds) {
						mSoundPool.play(mDohSound, 0.5f, 0.5f, 0, 0, 1.0f);
					}
					mLives--;
					mBoulders.remove(i);
					if(mLives < 0) {
						mGameOver = 100;
						mLives = 0;
						mPaused = true;
					}
					mStrLives = "" +mLives;
					mGameState = GAMESTATE_PLAYER_HIT;
					mStateTimer = TIME_PLAYER_HIT;
					mStartTime = System.currentTimeMillis();
				}
			}
		}
		}
	}
	
	private void drawBoulders(Canvas canvas) {
		for(int i = 0; i < mBoulders.size(); i++) {
			b = mBoulders.get(i);
			p = b.getPosition();
			r = b.getRadius();
			Matrix m = new Matrix();
			float scale = r / mTireCenter;
			int angle = (int)p.x;
			if(angle >= 360)
				angle -= 360;
			else if(angle < 0)
				angle += 360;
			
			m.preTranslate(-mTireCenter, -mTireCenter);
			m.preRotate(angle, mTireCenter, mTireCenter);
			m.preScale(scale, scale, mTireCenter, mTireCenter);
			m.postTranslate(p.x, p.y);
			canvas.drawBitmap(mTire, m, null);
		}
	}
	
	private void animatePlayer() {
		if(System.currentTimeMillis() - mLastAnimTime >= mAnimTime) {
			mAnimIndex++;
			if(mAnimIndex >= 8)
				mAnimIndex = 0;
			mLastAnimTime = System.currentTimeMillis();
		}
	}
	
	private void drawPlayer(Canvas canvas) {
		v = mPaddle.getVelocity();
		
		if(v.x == 0)
			mAnimIndex = 6;
		if(mPlayerState == PLAYER_CROUCHING)
			mAnimIndex = 8; 
		p = mPaddle.getPosition();
		if(mLastFrame != mAnimIndex) {
			int x = mAnimIndex * 60;
			Rect src = new Rect(x, 0, x+59, mPlayer.getHeight());
			Rect dst = new Rect(0, 0, mPlayerWidth, mPlayerHeight);

			mTempPlayer.eraseColor(0x00FFFFFF);
			
			mPlayerCanvas.drawBitmap(mPlayer, src, dst, mPaint);
			mLastFrame = mAnimIndex;
		}
		Matrix m = new Matrix();
		if(mDirection == FACING_LEFT)
			m.preScale(-1.0f, 1.0f, mPlayerWidth/2, mPlayerHeight/2);
		m.postTranslate(p.x, mHeight - mPlayerHeight);
//        mPaint.setColor(0xFF000000);
		canvas.drawBitmap(mTempPlayer, m, mPaint);
	}
	
	private void initSounds() {
		mBoingSound = mSoundPool.load(this.getContext(), R.raw.boing, 0);
		mDohSound = mSoundPool.load(this.getContext(), R.raw.doh, 0);
	}
	
	public void stop() {
        mRunning = false;
        mSoundPool.stop(mBoingSound);
        mSoundPool.stop(mDohSound);
        mSoundPool.unload(mBoingSound);
        mSoundPool.unload(mDohSound);
        mSoundPool.release();
	}
	
	private boolean collideWithPlayer(Point player, Point boulder, float r) {
		r = r*0.80f;
		if(boulder.y+r < player.y)
			return false;
		player.y = mHeight - mPlayerHeight;
		if(mPlayerState == PLAYER_CROUCHING)
			player.y += 20;
		if(boulder.x+r >= player.x+BOUNDING_BOX_FEATHER && boulder.x-r <= player.x+mPlayerWidth-BOUNDING_BOX_FEATHER)
			if(boulder.y+r >= player.y+BOUNDING_BOX_FEATHER && boulder.y-r <= player.y+mPlayerHeight-BOUNDING_BOX_FEATHER) {
				return true;
			}
		return false;
	}
	
	public void setSounds(boolean sounds) {
		mSounds = sounds;
	}
	
	public boolean getSounds() {
		return mSounds;
	}
	
	private void initGame() {
	    mLastBoulderTime = 0;
	    mAnimIndex = 0;
	    mLastAnimTime = 0;
	    mPlayerState = PLAYER_STANDING;
	    mDirection = FACING_LEFT;
	    mVelocityScale = DEFAULT_VELOCITY_SCALE;
	    mBouldersCleared = 0;
	    mLevelNumber = DEFAULT_LEVEL;
	    mNextLevel = 10;
	    mPaused = false;
	    mLastFrame = 0;
	    mLevelUp = 0;
	    mGameOver = 0;
	    mOneUp = 0;
	    mLives = DEFAULT_LIVES;
        mPaddle.setPosition((mWidth/2) - mPlayerWidth/2, mHeight - mPlayerHeight);
	    if(mBoulders.size() > 0)
	    	mBoulders.clear();
	    mStrLevel = "" + mLevelNumber;
	    mStrLives = "" + mLives;
	    mStrScore = "" + mBouldersCleared;
	    mGameState = GAMESTATE_BEGIN;
	    mStateTimer = TIME_BEGIN;
	    mStartTime = System.currentTimeMillis();
	}
	
	private void updateGameState() {
		if(mStateTimer == 0)
			return;
		
		if(System.currentTimeMillis() - mStartTime >= mStateTimer) {
			switch(mGameState) {
			case 1:
				mGameState = GAMESTATE_PLAYING;
				mStateTimer = 0;
				break;
			case 3:
				mGameState = GAMESTATE_PLAYING;
				mStateTimer = 0;
				break;
			case 4:
				mGameState = GAMESTATE_INTRO;
				mStateTimer = 0;
				break;
			}
		}
	}
}
